<?php
// +-------------------------------------------------------------------------+
// | PHP Version 5                                                           |
// +-------------------------------------------------------------------------+
// | ImaEngine - web development framework                                   |
// | Copyright (c) 2006-2007 Sardar Yumatov <ja.doma@gmail.com>              |
// +-------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify it |
// | under the terms of the GNU Lesser General Public License as published   |
// | by the Free Software Foundation; either version 2.1 of the License, or  |
// | (at your option) any later version.                                     |
// |                                                                         |
// | This library is distributed in the hope that it will be useful, but     |
// | WITHOUT ANY WARRANTY; without even the implied warranty of              |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                    |
// | See the GNU Lesser General Public License for more details.             |
// |                                                                         |
// | You should have received a copy of the GNU Lesser General Public        |
// | License along with this library; if not, write to the Free Software     |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
// +-------------------------------------------------------------------------+

/**
* File contains functions used for localization. Think about finding a localized resource, getting
* locale in wel expected format etc.
*
* Configuration used:
*   - [locale]str:language-expand.* -- expand language name (en) to locale (US) if omitted.
*   - [global]dir:root-dir -- installation root directory, used as default base dir for localized resources
*
* @author Sardar Yumatov <ja.doma@gmail.com>
* @copyright Copyright 2006 Sardar Yumatov
* @package common.l10n
*
* @PHP5Only
*/


require_once('lib/Configuration.php');
require_once('lib/FileUtils.php');

//determine user locales
if(!defined('PROCESS_LOCALE')) require_once('boot/locale.php');

/**
* This class is just a name-space for localization utilites.
*
* Some conventions:
*   - there are locales of the syntax [a-z]{2}_[A-Z]{2}
*   - there are languages of the syntax [a-z]{2} representing ISO-639-1 language codes
*
* When searching for a localized resource the system tries to find resource for requested
* locale or language. If resource is not found, then language only variant is used. If it
* fails too, then langauge only will be expanded to locale. If it fails, then first sibling
* locale will be used. If everything fails then exception will be thrown.
*
* @static
* @author Sardar Yumatov <ja.doma@gmail.com>
*/
class Localization {

	/**
	 * Returns true if given $str follows right syntax rules.
	 * Simple regexp based locale syntax check. If this method returns true, then
	 * subsequent call to <tt>parseLocale()</tt> will not throw exception.
	 *
	 * <b>Note</b>: use getLocale() if you want to normalize a locale.
	 *
	 * This method accepts non-strict syntax by default: [a-zA-Z]{2}([_\-][a-zA-Z]{2})?
	 * If $strict is set, then only [a-z]{2}(_[A-Z]{2})? will be seen as valid locale.
	 *
	 * @see getLocale()
	 * @param string $str string to check
	 * @param boolean $strict strict parsing
	 * @return boolean true if given locale is of right syntax, mentioned above
	 */
	public static function isLocale($str, $strict = false) {
		return $strict? preg_match('/^\s*[a-z]{2}(?:_[A-Z]{2})?\s*$/', $str): preg_match('/^\s*[a-zA-Z]{2}(?:[_\-][a-zA-Z]{2})?\s*$/');
	}

	/**
	 * Function parses given string as locale/language.
	 *
	 * This method parses non-strict locale syntax as defined by <tt>isLocale()</tt>.
	 * On success array with matches will be returned, with following keys:
	 *   - language => language code (en)
	 *   - locale => locale (en_US)
	 *   - short => true if only language was specified, false otherwise
	 *
	 * If $str is already parsed locale, then it will be returned as is.
	 *
	 * @throws InvalidArgumentException if locale is of wrong syntax or structure
	 * @param string|array $str input locale (en_Us, en-us, EN etc), null for default $locale['locale']
	 * @return array locale info
	 */
	public static function parseLocale($str=null) {
		global $locale;
		$str = empty($str)? $locale['locale']: $str;
		if(is_array($str)) {
			if(isset($str['short']) && isset($str['language']) && isset($str['locale'])) return $str;
			else throw new InvalidArgumentException("Given parsed locale doesn't contains required keys 'short', 'language', 'locale'!");
		}

		$mth=null;
		if(!preg_match('/^\s*([a-zA-Z]{2})(?:[_\-]([a-zA-Z]{2}))?\s*$/', $str, $mth)) throw new InvalidArgumentException("Given locale '{$str}' is of wrong syntax!");
		return array(
			'language' => strtolower($mth[1]),
			'locale' => isset($mth[2])? strtolower($mth[1]).'_'.strtoupper($mth[2]): strtolower($mth[1]).'_'.strtoupper(Configuration::get("[locale]str:language-expand.{$mth[1]}", strtoupper($mth[1]))),
			'short' => !isset($mth[2])
		);
	}

	/**
	 * Returns parsed locale.
	 *
	 * This method is equivalent to <tt>parseLocale()</tt>, but doesn't throw exceptions
	 * on wrong input. On any error null will be returned.
	 *
	 * @see parseLocale()
	 * @param string|array $str input locale (en_Us, en-us, EN etc), null for default $locale['locale']
	 * @return array locale info
	 */
	public static function getLocale($str=null) {
		try { return self::parseLocale($str); }
		catch (InvalidArgumentException $e) { trigger_error($e->getMessage(), E_USER_NOTICE); }
		return null;
	}

	/**
	 * Convert short language name to full locale name.
	 *
	 * Simple function that extends the given 2 letters language code (ru) to full locale name (ru_RU)
	 * according to [locale]language-expand.* configuration (e.g. en -> en_US). If config is not set,
	 * then language will simply be used as locale in uppercase (e.g. ru -> ru_RU)
	 *
	 * If full locale is already given, then string will be returned untouched (trim()'ed).
	 * @throws InvalidArgumentException if $lang is not a language or locale
	 * @param string $lang input language or locale (en_Us, en-us, EN etc)
	 * @return string full locale name (en_US)
	 */
	public static function languageToLocale($lang) {
		$mth=null;
		if(!preg_match('/^\s*([a-zA-Z]{2})(?:[_\-]([a-zA-Z]{2}))?\s*$/', $lang, $mth))
			throw new InvalidArgumentException("Given language '{$lang}' is not a ISO-639-1 language code");
		return isset($mth[2])? strtolower($mth[1]).'_'.strtoupper($mth[2]): strtolower($mth[1]).'_'.strtoupper(Configuration::get("[locale]str:language-expand.{$mth[1]}", strtoupper($mth[1])));
	}


	/**
	 * Returns localized file.
	 *
	 * Method tries to find localized $resource by rewriting given path.
	 * Following conventions apply:
	 *   - method rewrites name of the latest path entry as returned by <tt>FilePath::getFile()</tt>
	 *   - locale or language prefix will be added to the name, separated by the dot '.' (test.txt -> ru.test.txt)
	 *   - method adds language to the $base if $flat mode is not set, thus all resources from the base will be organized in language subdirectories
	 *   - relative $resource may not point higher than the $base (strict mode is set)
	 *   - both files and directories are seen as resources
	 *
	 * Function implements generic resource localization algorithm which is:
	 *  - parse locale
	 *  - if locale was given -> try to find by locale
	 *  - try to find by language
	 *  - map language to (usual) locale, if it != given locale, then try that locale (means mapped locales are preffered for languages)
	 *  - try to find first (random) sibling locale of the same language
	 *
	 * In other words the resource will always be in requested language, but
	 * if requested locale (or language only version) is not available, then
	 * sibling locale will be used.
	 *
	 * The base directory is by default from config <tt>[global]dir:root-dir</tt>.
	 *
	 * Examples of input ($resource, $base, $flat -> result):
	 *  - test/bla -> root-dir/ru/test/ru_RU.bla
	 *  - test/bla /some/path -> /some/path/ru/test/ru_RU.bla
	 *  - test/bla /some/path true -> /some/path/test/ru_RU.bla
	 *  - /test/bla -> may throw exception because $resource may points higher than the $base which is 'root-dir' directory by default
	 *  - /test/bla / -> /ru/test/ru_RU.bla
	 *  - /test/bla / true -> /test/ru_RU.bla
	 *
	 * The method will write resulting locale/language to $chosen reference.
	 *
	 * @throws NotFoundException if resource isn't found
	 * @throws InstallationException if base directory doesn't exist
	 * @throws BasePathException if resource tries to point higher than the $base
	 *
	 * @param string|FilePath $resource resource path, may not point higher than the $base
	 * @param string|FilePath $base base directory, where localized files reside, shall be absolute, null for default <tt>[global]dir:root-dir</tt>
	 * @param boolean $flat do not use language sub-directories
	 * @param string|array $loc language or locale name, null for default $locale['locale']
	 * @param string $chosen chosen locale/language will be saved in this variable
	 * @return string path to localized resource
	 */
	public static function getLocalizedResource($resource, $base=null, $flat=false, $loc=null, &$chosen=null) {
		$loc = self::parseLocale($loc); //throws InvalidArgumentException

		$base = is_null($base)? Configuration::getOption('[global]dir:root-dir'): $base; //throws InstallationException, ConfigurationException
		$fbase = FilePath::createPath(FileUtils::ensureDir(($flat? '.': $loc['language']), $base), true); //throws InstallationException
		$fresource = FilePath::createPath($resource, true, $fbase, true); //throws BasePathException

		$res = $fresource->getFile();
		$resbase = $fresource->getBase();
		$len = strlen($res);
		//OK, now try to find one: locale, langauge, expanded,  first sibling locale
		if($loc['short'] || !is_file($path = $resbase.DIRECTORY_SEPARATOR.($chosen = $loc['locale']).'.'.$res)) { //OK, try language
			if(!is_file($path = $resbase.DIRECTORY_SEPARATOR.($chosen = $loc['language']).'.'.$res)) { //OK, try common expand
				if(($ll = self::languageToLocale($loc['language'])) == $loc['locale'] || !is_file($path = $resbase.DIRECTORY_SEPARATOR.($chosen = $ll).'.'.$res)) { //try first sibling
					$dir = dir($resbase);
					while(false !== ($entry = $dir->read())) {
						//"en_US.".length() = 6
						if((strlen($entry) != $len + 6) || substr($entry, 0, 2) != $loc['language'] || substr($entry, -$len, $len) != $resource) continue;
						if(!preg_match('/^[a-z]{2}_[A-Z]{2}$/', ($chosen = substr($entry, 0, 5)))) continue; //another bogus file?
						return $resbase.DIRECTORY_SEPARATOR.$entry;
					}
					//giving up...
					$resource = FileUtils::getPath($resource);
					$base = FileUtils::getPath($base);
					$chosen = null;
					throw new NotFoundException("Requested resource '{$resource}' isn't found in base '{$base}'");
				}
			}
		}
		return $path;
	}

	/**
	 * Returns localized $resource if it exists or $resource as-is if localized version is not found.
	 * This method equals to <tt>getLocalizedResource()</tt> but tries to locate $resource without
	 * name rewriting if no localized version is found.
	 *
	 * @see getLocalizedResource()
	 * @throws NotFoundException if no localized nor as-is $resource is found
	 * @throws InstallationException if $base directory doesn't exist
	 * @throws BasePathException if $resource tries to point higher than the $base
	 *
	 * @param string|FilePath $resource resource path, may not point higher than the $base
	 * @param string|FilePath $base base directory, where localized files reside, shall be absolute, null for default <tt>[global]dir:root-dir</tt>
	 * @param boolean $flat do not use language sub-directories
	 * @param string|array $loc language or locale name, null for default $locale['locale']
	 * @param string $chosen chosen locale/language will be saved in this variable, null if non localized version is used
	 * @return string path to localized or non-localized (fallback) resource
	 */
	public static function tryLocalizedResource($resource, $base=null, $flat=false, $loc=null, &$chosen=null) {
		try {return self::getLocalizedResource($resource, $base, $flat, $loc, $chosen);}
		catch (NotFoundException $e) {
			$base = is_null($base)? Configuration::getOption('[global]dir:root-dir'): $base; //throws InstallationException, ConfigurationException
			$chosen = null;
			if(file_exists($path = FileUtils::absolutePath($resource, $base, true))) return $path;
			$resource = FileUtils::getPath($resource); $base = FileUtils::getPath($base);
			throw new NotFoundException("No localized nor as-is resource is found: {$resource}! Base: {$base}");
		}
	}

	/**
	 * Tries to find localized $resource for any locale from $locales.
	 *
	 * This method will iterate <tt>getLocalizedResource()</tt> for all locales from <tt>$locales</tt>. If localized
	 * resource is found, then $chosen will be set to matching locale or language and method will return resolved against
	 * $base path to resource. Otherwise method tries non-localized resource like <tt>tryLocalizedResource()</tt>
	 *
	 * @see getLocalizedResource()
	 * @throws NotFoundException if no localized nor as-is $resource is found
	 * @throws InstallationException if $base directory doesn't exist
	 * @throws BasePathException if $resource tries to point higher than the $base
	 *
	 * @param string|FilePath $resource resource path, may not point higher than the $base
	 * @param string|FilePath $base base directory, where localized files reside, shall be absolute, null for default <tt>[global]dir:root-dir</tt>
	 * @param boolean $flat do not use language sub-directories
	 * @param array $locales list of locales to check, sorted by priority from highest to lowest, by default $locale['locales']
	 * @param string $chosen chosen locale/language will be saved in this variable, null if non localized version is used
	 * @return string path to localized or non-localized (fallback) resource
	 */
	public static function anyLocalizedResource($resource, $base=null, $flat=false, $locales=null, &$chosen=null) {
		global $locale;
		$locales = is_null($locales)? $locale['locales']: $locales;
		if(!is_array($locales) && !($locales instanceof Iterator) && !($locales instanceof IteratorAggregate))
			throw new InvalidArgumentException("List of locales is expected (ether array or iterable), but '{$locales}' is given instead!");

		foreach ($locales as $loc) {
			try {
				return self::getLocalizedResource($resource, $base, $flat, $loc, $chosen);
			} catch (NotFoundException $e) {
				//ignoring, chose another one
			}
		}

		$base = is_null($base)? Configuration::getOption('[global]dir:root-dir'): $base; //throws InstallationException, ConfigurationException
		$chosen = null;
		if(file_exists($path = FileUtils::absolutePath($resource, $base, true))) return $path;
		$resource = FileUtils::getPath($resource); $base = FileUtils::getPath($base);
		throw new NotFoundException("No localized nor as-is resource is found: {$resource}! Base: {$base}");
	}

	/**
	 * Create path to localized resource for further installation.
	 *
	 * This method is similar to <tt>getLocalizedResource()</tt>, but doesn't search a resource.
	 * The method simply builds the path where localized resource should reside according to the localization rules
	 * for locale/language given in $loc. No existence check will be done, so user logic must ensure file
	 * doesn't exist before installing a new resource.
	 *
	 * @see getLocalizedResource()
	 * @throws BasePathException if resource tries to point higher than the $base
	 *
	 * @param string|FilePath $resource resource path, may not point higher than the $base
	 * @param string|FilePath $base base directory, where localized files should reside, shall be absolute, null for default <tt>[global]dir:root-dir</tt>
	 * @param boolean $flat do not use language sub-directories
	 * @param string|array $loc language or locale name, null for default $locale['locale']
	 * @return string path where localized resource should reside
	 */
	public static function createLocalizedResource($resource, $base= null, $flat=false, $loc=null) {
		$loc = self::parseLocale($loc);
		$base = is_null($base)? Configuration::getOption('[global]dir:root-dir'): $base; //throws InstallationException, ConfigurationException
		$fbase = FilePath::createPath($flat? '.': $loc['language'], true, FilePath::createPath($base, true), true); //throws InstallationException
		$fresource = FilePath::createPath($resource, true, $fbase, true); //throws BasePathException
		return $fresource->getBase().DIRECTORY_SEPARATOR.($loc['short']? $loc['language']: $loc['locale']).'.'.$fresource->getFile();
	}

	/**
	 * Returns path to the localized resource.
	 *
	 * This function implements another convention for localized resources than
	 * <tt>getLocalizedResource()</tt> function. The $resouce must reside in language
	 * or locale subdirectory of the $base. This means the $resource may not be absolute path.
	 *
	 * Following algorithm is used when searching:
	 *  - parse locale
	 *  - if locale was given -> try $base/{locale}/$resource
	 *  - try $base/{language}/$resource
	 *  - map language to this_locale, if locale was given and given_locale != this_locale, then try $base/this_locale/$resource
	 *  - try if $base/{first sibling locale}/$resource
	 *
	 * Method will write resulting locale/language to $chosen reference.
	 *
	 * @throws NotFoundException if $resource is not found
	 * @throws InstallationException if $base directory doesn't exist
	 * @throws BasePathException if $resource tries to point higher than the $base
	 *
	 * @param string|FilePath $base base (root) path were localized directories reside
	 * @param string|FilePath $resource relative path to file or directory
	 * @param string|array $loc optional locale, if null, then global $locale['locale'] is used instead
	 * @param string $chosen chosen locale/language will be saved in this variable
	 * @return string path to the localized resource
	 */
	public static function getLocalizedPath($base, $resource, $loc=null, &$chosen=null) {
		$loc = self::parseLocale($loc);
		$base = FileUtils::ensureDir($base); //throws InstallationException

		$fresource = FilePath::createPath($resource);
		if($fresource->isAbsolute()) throw new InvalidArgumentException("Target is absolute {$resource}, this is not allowed!");
		if($fresource->isUnsafe()) throw new BasePathException("Target '{$resource}' tries to point higher than the '{$base}'! Security check not passed!");

		$res = $fresource->getPath(true);
		//OK, now try to find one: locale, langauge, expanded, first sibling locale
		if($loc['short'] || !file_exists($path = $base.DIRECTORY_SEPARATOR.($chosen = $loc['locale']).DIRECTORY_SEPARATOR.$res)) { //try language
			if(!file_exists($path = $base.DIRECTORY_SEPARATOR.($chosen = $loc['language']).DIRECTORY_SEPARATOR.$res)) { //OK, try common expand
				if(($ll = self::languageToLocale($loc['language'])) == $loc['locale'] || !file_exists($path = $base.DIRECTORY_SEPARATOR.($chosen = $ll).DIRECTORY_SEPARATOR.$res)) { //try first sibling
					//try to find first sibling
					$dir = dir($base);
					while(false !== ($entry = $dir->read())) {
						if(strlen($entry) != 5 || substr($entry, 0, 2) != $loc['language'] || !preg_match('/^[a-z]{2}_[A-Z]{2}$/', $entry)) continue;
						if(!file_exists($path = $base.DIRECTORY_SEPARATOR.$entry.DIRECTORY_SEPARATOR.$res)) continue;
						return FileUtils::absolutePath(($chosen = $entry).DIRECTORY_SEPARATOR.$res, $base);
					}
					//giving up...
					throw new NotFoundException("Requested resource '{$resource}' isn't found in base '{$base}'");
				}
			}
		}
		return FileUtils::absolutePath($path);
	}

	/**
	 * Returns localized $resource if it exists or $resource as-is if localized version is not found.
	 * This method equals to <tt>getLocalizedPath()</tt> but tries $resource as is without
	 * rewriting if no localized version was found.
	 *
	 * @see getLocalizedPath()
	 * @throws NotFoundException if no localized nor as-is $resource is not found
	 * @throws InstallationException if $base directory doesn't exist
	 * @throws BasePathException if $resource tries to point higher than the $base
	 *
	 * @param string|FilePath $base base (root) path were localized directories reside
	 * @param string|FilePath $resource relative path to file or directory
	 * @param string|array $loc optional locale, if null, then global $locale['locale'] is used instead
	 * @param string $chosen chosen locale/language will be saved in this variable, null if no localized resource was found
	 * @return string path to the localized or non-localized (fallback) resource
	 */
	public static function tryLocalizedPath($base, $resource, $loc=null, &$chosen=null) {
		try {return self::getLocalizedPath($base, $resource, $loc, $chosen);}
		catch (NotFoundException $e) {
			$chosen = null;
			if(file_exists($path = FileUtils::absolutePath($resource, $base, true))) return $path;
			$resource = FileUtils::getPath($resource); $base = FileUtils::getPath($base);
			throw new NotFoundException("No localized nor as-is resource is found: {$resource}! Base: {$base}");
		}
	}

	/**
	 * Tries to find localized $resource for any locale from $locales.
	 *
	 * This method will iterate <tt>getLocalizedPath()</tt> for all locales from <tt>$locales</tt>. If localized
	 * resource is found, then $chosen will be set to matching locale or language and method will return resolved against
	 * $base path. Otherwise method tries non-localized resource like <tt>tryLocalizedPath()</tt>
	 *
	 * @see getLocalizedPath()
	 * @throws NotFoundException if no localized nor as-is $resource is not found
	 * @throws InstallationException if $base directory doesn't exist
	 * @throws BasePathException if $resource tries to point higher than the $base
	 *
	 * @param string|FilePath $base base (root) path were localized directories reside
	 * @param string|FilePath $resource relative path to file or directory
	 * @param array $locales list of locales to check, sorted by priority from highest to lowest, by default $locale['locales']
	 * @param string $chosen chosen locale/language will be saved in this variable, null if no localized resource was found
	 * @return string path to the localized or non-localized (fallback) resource
	 */
	public static function anyLocalizedPath($base, $resource, $locales=null, &$chosen=null) {
		global $locale;
		$locales = is_null($locales)? $locale['locales']: $locales;
		if(!is_array($locales) && !($locales instanceof Iterator) && !($locales instanceof IteratorAggregate))
			throw new InvalidArgumentException("List of locales is expected (ether array or iterable), but '{$locales}' is given instead!");

		foreach($locales as $loc) {
			try {
				return self::getLocalizedPath($base, $resource, $loc, $chosen);
			} catch (NotFoundException $e) {
				//ignoring, chose another one
			}
		}

		$chosen = null;
		if(file_exists($path = FileUtils::absolutePath($resource, $base, true))) return $path;
		$resource = FileUtils::getPath($resource); $base = FileUtils::getPath($base);
		throw new NotFoundException("No localized nor as-is resource is found: {$resource}! Base: {$base}");
	}

	/**
	 * Create localized path for further installation.
	 *
	 * This method simply builds the path as defined by <tt>getLocalizedPath()</tt>.
	 * No searchin nor existing check will be performed. Receiver should ensure the path
	 * dosn't point to existing resource before installation.
	 *
	 * @see getLocalizedPath()
	 * @throws BasePathException if $target tries to point higher than the $base
	 * @param string|FilePath $base base (root) path were localized directories reside
	 * @param string|FilePath $target relative path to file or directory
	 * @param string|array $loc optional locale, if null, then global $locale['locale'] is used instead
	 * @return string path where localized resource should reside
	 */
	public static function createLocalizedPath($base, $target, $loc=null) {
		$loc = self::parseLocale($loc);
		$fbase = FilePath::createPath(($loc['short']? $loc['language']: $loc['locale']), true, FilePath::createPath($base, true), true); //throws UnresolvedException, BasePathException
		$ftarget = FilePath::createPath($target, true, $fbase, true); //throws BasePathException
		return $ftarget->getPath();
	}


	//========================- Mostly for internal use -=========================
	/**
	 * Parse locale string defined in Accept-Language format.
	 * Read more in boot/locale_info.php script about this function.
	 *
	 * All locales will be sorted by priority. If priority is not set, then
	 * 1 (highest) priority will be used. Locales with highest priority will be listed
	 * in order they were read from $str.
	 *
	 * Function returns 2 dimensional array with locales (key 'locales') and languages (key 'languages').
	 * Each array is associative, where keys are equal to values, but the order (foreach, next() etc) is
	 * from highest priority entry to lowest.
	 *
	 * @param string $str locales together with priorities
	 * @return array 2 dimensional array with locales and languages sorted by priority to lowest
	 */
	public static function parseLocaleString($str) {
		$locbuf=array(); $langbuf=array();
		$locpriorbuf=array(); $langpriorbuf=array();

		$mtch=null;
		foreach (explode(',', $str) as $entry) {
			if(!preg_match('/^([a-zA-Z]{2})(?:[_\\-]([a-zA-Z]{2}))?(?:\s*;\s*q\s*=\s*([0-9\\.]+))?\s*$/', trim($entry), $mtch)) continue; //ignore wrong syntax

			$language = strtolower($mtch[1]); // (en)
			if(!empty($mtch[2])) $locale = $language.'_'.strtoupper($mtch[2]); //full locale name (en_US)
			else $locale = self::languageToLocale($language);
			$prior=isset($mtch[3])? floatval($mtch[3]): 1;

			if(!isset($locbuf[$locale])) { //process locales
				if($prior >= 1) {
					$locbuf[$locale]=$locale; //we got the locale in right place, disable further processing
					unset($locpriorbuf[$locale]); //dsable priority counter
				} elseif(!isset($locpriorbuf[$locale]) || $prior > $locpriorbuf[$locale]) $locpriorbuf[$locale]=$prior;
			}

			if(!isset($langbuf[$language])) { //process languages
				if($prior >= 1) {
					$langbuf[$language]=$language;
					unset($langpriorbuf[$language]);
				} elseif(!isset($langpriorbuf[$language]) || $prior > $langpriorbuf[$language]) $langpriorbuf[$language]=$prior;
			}
		}

		//sort delayed langs by priority
		//[XXX]entries with equal priority will be in undefined order! always set for domain language the priority higher(0.2) than default language(0.1)!
		arsort($locpriorbuf);
		arsort($langpriorbuf);

		//add sorted by priority
		//warning: if array_keys change the order, then this code will break!
		foreach(array_keys($locpriorbuf) as $entry) $locbuf[$entry]=$entry;
		foreach(array_keys($langpriorbuf) as $entry) $langbuf[$entry]=$entry;
		return array('languages'=>$langbuf, 'locales'=>$locbuf);
	}
}
?>